Design System
The single source of truth for Rapid across pilot mobile and tablet. Built on the shadcn/ui component architecture and token conventions, themed with the Rapid brand — IBM Plex Sans/Mono and the cockpit-navy palette. Tailwind class names map directly to the CSS variables documented here.
Color & tokens
Semantic tokens follow shadcn/ui naming exactly — background / foreground pairs, primary, muted, accent, border / input / ring. Reference the variable, never the hex. Toggle the theme (top-left) to preview both modes live.
Status / semantic
Status tints: hue at 12–14% alpha for fills, 30–40% for borders. Status is always reinforced by text or icon — never color alone (accessibility).
Typography
Spacing, radius & elevation
Factor 4 — every token equals its step number × 4px (--space-N = N × 4px). Every padding, margin, and gap must use a --space-* token — never a hardcoded pixel value. Base radius token --radius: 12px.
/* correct */ gap: var(--space-3); padding: var(--space-4) var(--space-6); /* wrong */ gap: 12px; padding: 16px 24px;
SPACING
RADIUS
ELEVATION
Components
Every component is a Rapid-themed shadcn/ui primitive. Each entry below shows live variants, a props reference, and the shadcn/Tailwind implementation. Interactive playgrounds live in the Storybook.
Input & field
<Label/> + <Input/>A field = label + control + helper. Numeric/aviation values use the mono face and a unit suffix. Helper text carries the valid range (copied identically across mobile & tablet). Error state swaps the border and helper to --destructive.
<div className="space-y-2">
<Label htmlFor="gw">Gross weight</Label>
<div className="flex items-center gap-2">
<Input id="gw" inputMode="numeric" className="font-mono" />
<span className="text-muted-foreground">kg</span>
</div>
<p className="text-xs text-muted-foreground">range 34000–61234</p>
</div>Select & dropdown
<Select/> · <DropdownMenu/>Trigger shows the current value + chevron. The popover lists options with a check on the selected row; hover uses --accent. Used for Flaps, Pack, ENG AIL, Runway condition, NOTAM runway.
<Select value={cond} onValueChange={setCond}>
<SelectTrigger><SelectValue/></SelectTrigger>
<SelectContent>
{["Dry","Wet","Slush","Stand Water","Dry Snow"].map(o =>
<SelectItem key={o} value={o}>{o}</SelectItem>)}
</SelectContent>
</Select>Toggles & choice
Switch · Tabs(segmented) · RadioGroupSwitch for on/off prefs (keep-awake, biometric). Segmented control for 2–3 mutually-exclusive options (theme, units, density). Radio rows for longer single-select lists (language).
<Switch checked={autoMetar} onCheckedChange={setAutoMetar} />
<Tabs value={theme} onValueChange={setTheme}>
<TabsList><TabsTrigger value="dark">Dark</TabsTrigger>…</TabsList>
</Tabs>Badge & status
<Badge variant />Status pills always pair a tint fill with a text label — never color alone. Runway result status (OK / EXCD), update state, NOTAM/MEL chips, and AIRAC currency all use this primitive.
Card & list row
<Card/> · rowCards group related data on --card. List rows (aircraft, airports, settings) share a leading icon · title + meta · trailing control/chevron layout with 44px+ height.
AIRAC 2608-02 · to 21 Jul
Tabs & navigation
bottom-nav · side-rail · tabsMobile uses a bottom tab bar; tablet uses a persistent side rail. Same destinations, same icons — layout adapts, vocabulary doesn't.
Dialog & sheet
<AlertDialog/> · <Sheet/>Destructive actions use an AlertDialog (cancel + confirm) gated by the “Confirm before delete” preference. Bottom sheets host the airport picker, MEL list, and NOTAM list on mobile; tablet uses a centered dialog.
Toast
toast(message)Transient confirmation, ~2.4s auto-dismiss, bottom-centered. Used after add/delete/select actions and async results.
Table
<Table/> · runway resultsData tables use the mono face for all numeric columns and a status badge per row. Rows are tappable to open runway detail.
Checkbox
<Checkbox/>Boolean field for multi-select forms. Always paired with a visible label; minimum 44 px touch target. Indeterminate state for partial-select headers.
<div className="flex items-center gap-3">
<Checkbox id="ep-fsi" checked={checked} onCheckedChange={setChecked} />
<Label htmlFor="ep-fsi">EP-FSI</Label>
</div>Avatar
<Avatar/> · AvatarFallbackUser identity mark. Displays a profile image with an initials fallback. Status dot overlays the bottom-right corner: success = online, warning = busy.
<Avatar>
<AvatarImage src={profileUrl} alt="HK" />
<AvatarFallback>HK</AvatarFallback>
</Avatar>Chip & tag
inline · removable · filterCompact inline labels for active filters and selected items. Removable chips dismiss a single selection with an ×. Used for MEL and NOTAM inclusions on the takeoff form.
<Badge variant="info" className="rounded-full">NOTAM active</Badge>
<Badge variant="warning" className="rounded-full flex gap-1">
MEL ×1 <X size={10} className="cursor-pointer" />
</Badge>Progress bar
<Progress value />Linear indicator for long-running operations. Determinate shows a fill; indeterminate uses the shimmer sweep. Used in the update-flow progress per aircraft and AIRAC cycle.
<Progress value={72} />
<Progress value={100} className="[&>div]:bg-success" />
<Progress /> {/* indeterminate */}Accordion
<Accordion/> · collapsibleDisclosure sections with a chevron trigger (min 44 px). Used in the runway info panel — CONDITIONS, CONFIGURATION, LIMITATION, OPERATIONAL, OBSTACLES, MEL/CDL each collapse to save vertical space.
<Accordion type="single" collapsible>
<AccordionItem value="conditions">
<AccordionTrigger>CONDITIONS</AccordionTrigger>
<AccordionContent>Wind · OAT · QNH · Rwy cond</AccordionContent>
</AccordionItem>
</Accordion>FAB
Floating Action ButtonPersistent elevated CTA for the most frequent action on a screen. Mobile Flights list: "New flight". Floats bottom-right above the tab bar. Extends to a labelled pill when space allows.
<Button variant="default" size="icon" className="rounded-full h-12 w-12 shadow-md fixed bottom-24 right-6"> <Plus /> </Button>
METAR display
MetarCard · parsedAviation weather block. Raw METAR string in mono type followed by parsed fields. Wind, visibility, OAT, QNH, and ceiling feed directly into the performance calculation.
<MetarCard icao="EGCC"
raw="EGCC 250820Z 36006KT 9999 FEW030 BKN075 18/10 Q1013"
parsed={parsedMetar} />V-speed strip
VSpeedStrip · V1 VR V2Primary output of a takeoff calculation. V1, VR, V2, VFTO, and N1 in large mono type with per-runway status badge. EXCD variant highlights exceeded weights in destructive.
<VSpeedStrip runway="05" status="ok"
v1={138} vr={142} v2={148} vfto={165} n1={90.4}
weight={62480} flaps={5} />Runway diagram
RunwayDiagram · compassCompass rose with runway orientation and wind arrow. Aircraft icon aligned to the departure threshold. Provides immediate spatial context for wind cross/tail component.
<RunwayDiagram
runwayHeading={50} windDirection={360} windSpeed={6}
activeRunway="05" />Molecular components
Composed of two or more atoms. Each is a self-contained functional unit with its own props contract.
Segmented control
<SegmentedControl options value />Connected pill-group for mutually exclusive options. Used for TAKEOFF/LANDING and OAT/ASSTEM switches in the pilot apps. Visually joined segments, selected segment is filled.
<SegmentedControl
options={["TAKEOFF","LANDING"]}
value={mode}
onChange={setMode}
/>Combobox
<Combobox placeholder options />Search input with live-filtered results list. Used for airport ICAO search and aircraft registration search. Composed of an Input atom + a popover results list — distinct from Select (no free-text entry in Select).
<Combobox
placeholder="Search ICAO…"
options={airports}
onSelect={(icao) => setAirport(icao)}
/>
// Built on shadcn Command + PopoverTab strip
<TabStrip tabs activeTab />Row of underline-style tabs for admin-surface navigation (messages, company profile sections, data management). Sits on a border-bottom container; each tab uses the TabUnderline atom.
<TabStrip
tabs={["Contracts","Aircraft","Updates","Users"]}
activeTab={active}
onChange={setActive}
/>Bottom sheet
<BottomSheet open title />Slide-up modal panel from the bottom of the screen. Primary modal pattern on mobile (vs. centered dialog on tablet/admin). Drag handle, backdrop overlay, animated entry.
<BottomSheet open={open} onClose={() => setOpen(false)}
title="Add NOTAM">
{/* content */}
</BottomSheet>
// Mobile only — tablet uses DialogKPI card
<KpiCard value label variant />Single metric stat card showing a numeric value, label, optional icon, and semantic color variant. Used in admin dashboard grids and the super-admin monitoring panel.
<KpiCard value="47" label="Active aircraft" hint="Across all fleets" variant="info" /> <KpiCard value="3" label="Expiring soon" variant="warn" />
Feed item
<FeedItem icon title body time />Single timestamped activity event row used in notification inboxes and audit logs. Unread state adds --info left border and bold title. Icon slot accepts any status icon.
<FeedItem
icon={<FileIcon />} variant="info"
title="New contract ready for review"
body="SkyJet Europe submitted…"
time="09:14" unread />OTP step
<OtpStep step label done />Numbered signing step card used in the e-signature flow. Two states: pending (shows digit inputs) and done (shows checkmark). Each step collects a 6-digit OTP from one party.
<OtpStep step={1} label="Airline approval" done
signedBy="SkyJet Europe" signedAt="14 Jul" />
<OtpStep step={2} label="Rapid approval"
email="admin@rapid.aero" />Update item
<UpdateItem label progress status />Single download queue row showing label, progress bar, and status. Used inside UpdateCenter. Three states: pending, downloading (with % progress), complete.
<UpdateItem label="B737-800 perf file" progress={68} status="downloading" />
<UpdateItem label="AIRAC 2608-02" progress={100} status="done" />
<UpdateItem label="MD-90 perf file" status="pending" />Atomic components
Single-purpose, indivisible UI elements. Each exists in both the pilot apps and admin surfaces.
Connectivity dot
<ConnectivityDot status />Inline status chip shown in the top nav to communicate real-time network state. Uses text + icon so it is never color-only. Online = --success tint, Offline = --destructive tint.
<ConnectivityDot status="online" /> <ConnectivityDot status="offline" />
Callout
<Callout variant />Inline alert banner for workflow guidance, warnings, or rejection feedback. Four semantic variants. Always uses icon + text — never color alone.
<Callout variant="info">Sign in both directions.</Callout> <Callout variant="warn">AIRAC expires soon.</Callout> <Callout variant="error">Contract rejected.</Callout> <Callout variant="success">Files published.</Callout>
Tab (underline)
<TabUnderline label active />Single tab with underline-style active indicator. Used in a row (TabStrip) for admin-surface navigation. Distinct from the pill/chip tabs used in pilot apps.
<TabUnderline label="Inbox" active /> <TabUnderline label="Tickets" />
OTP digit
<OtpDigit value />Single digit input cell for 6-digit OTP entry during contract e-signature. Filled state shows the digit; empty state shows a blinking cursor placeholder.
<OtpDigit value="3" /> {/* filled */}
<OtpDigit value="" /> {/* empty */}
<OtpDigit value="" focused /> {/* active */}Upload box
<UploadBox accept onUpload />Drag-and-drop file upload zone with dashed border. Used for AFM performance file uploads and aircraft registration documents in the admin panel.
<UploadBox
accept=".pdf,.zip"
onUpload={(files) => handleUpload(files)}
hint="PDF or ZIP · max 50 MB"
/>Subscription ring
<SubscriptionRing pct days />SVG donut ring showing subscription days remaining as a percentage. Displayed in the tablet top bar. Color transitions: success → warning → destructive as the subscription nears expiry.
<SubscriptionRing pct={85} days={312} />
{/* color: success >30%, warning 10–30%, destructive <10% */}UI states
Every async surface and every list must define four states. Examples below use real app screens.
Rule: empty states for every list (flights, NOTAMs, MELs, search results); skeletons for every async list; inline error + retry that preserves cached data; success via toast for small actions or a confirmation panel for multi-step flows.
App patterns
Dev handoff
// tailwind.config / globals.css — drop-in shadcn theme
:root {
--background:222 47% 6%; --foreground:210 16% 84%;
--primary:213 33% 94%; --primary-foreground:222 50% 8%;
--muted-foreground:208 12% 54%; --border:212 31% 16%;
--ring:213 30% 78%; --radius:0.7rem;
}
// then: npx shadcn@latest add button input select switch dialog ...components/ui/*)..light / system via next-themes. Never hard-code hex in components.